/*!********************************************************************

  Audacity: A Digital Audio Editor

  @file AudioUnitEffectsModule.cpp

  Dominic Mazzoni
  Leland Lucius

  Paul Licameli split from AudioUnitEffect.

**********************************************************************/

#if USE_AUDIO_UNITS
#include "AudioUnitEffectsModule.h"

#include "AudioUnitEffectBase.h"
#include "ModuleManager.h"
#include <wx/log.h>
#include <wx/tokenzr.h>

#include "wxArrayStringEx.h"
#include <wx/osx/core/private.h>

static const struct
{
    OSType componentManufacturer;
    OSType componentType;
    OSType componentSubType;
}
BlackList[] =
{
    { 'appl', 'augn', 'afpl' },  // Apple: AUAudioFilePlayer
    { 'appl', 'augn', 'sspl' },  // Apple: AUScheduledSoundPlayer
    { 'appl', 'augn', 'ttsp' },  // Apple: AUSpeechSynthesis
    { 'appl', 'augn', 'nrcv' },  // Apple: AUNetReceive
    { 'appl', 'aumx', '3dmx' },  // Apple: AUMixer3D
    { 'appl', 'aumx', 'mspl' },  // Apple: AUMultiSplitter
    { 'appl', 'aumx', 'mcmx' },  // Apple: AUMultiChannelMixer
    { 'appl', 'aumx', 'mxmx' },  // Apple: AUMatrixMixer
    { 'appl', 'aumx', 'smxr' },  // Apple: AUMixer
    { 'Ignt', 'aufx', 'PTQX' },  // Ignite Amps PTEq-X
};

// ============================================================================
// Module registration entry point
//
// This is the symbol that Audacity looks for when the module is built as a
// dynamic library.
//
// When the module is builtin to Audacity, we use the same function, but it is
// declared static so as not to clash with other builtin modules.
// ============================================================================
DECLARE_PROVIDER_ENTRY(AudacityModule)
{
    // Create and register the importer
    // Trust the module manager not to leak this
    return std::make_unique<AudioUnitEffectsModule>();
}

// ============================================================================
// Register this as a builtin module
// ============================================================================
DECLARE_BUILTIN_PROVIDER(AudioUnitEffectsBuiltin);

///////////////////////////////////////////////////////////////////////////////
//
// AudioUnitEffectsModule
//
///////////////////////////////////////////////////////////////////////////////

namespace {
wxString FromOSType(OSType type)
{
    OSType rev = (type & 0xff000000) >> 24
                 | (type & 0x00ff0000) >> 8
                 | (type & 0x0000ff00) << 8
                 | (type & 0x000000ff) << 24;

    return wxString::FromUTF8(reinterpret_cast<char*>(&rev), 4);
}

OSType ToOSType(const wxString& type)
{
    wxCharBuffer buf = type.ToUTF8();

    OSType rev = ((unsigned char)buf.data()[0]) << 24
                 | ((unsigned char)buf.data()[1]) << 16
                 | ((unsigned char)buf.data()[2]) << 8
                 | ((unsigned char)buf.data()[3]);

    return rev;
}

AudioComponent FindAudioUnit(const PluginPath& path,
                             wxString& name)
{
    wxStringTokenizer tokens(path, wxT("/"));

    AudioComponentDescription desc;

    desc.componentManufacturer = ToOSType(tokens.GetNextToken());
    desc.componentType = ToOSType(tokens.GetNextToken());
    desc.componentSubType = ToOSType(tokens.GetNextToken());
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;

    name = tokens.GetNextToken();
    return AudioComponentFindNext(NULL, &desc);
}
}

AudioUnitEffectsModule::AudioUnitEffectsModule()
{
}

AudioUnitEffectsModule::~AudioUnitEffectsModule()
{
}

// ============================================================================
// ComponentInterface implementation
// ============================================================================

PluginPath AudioUnitEffectsModule::GetPath() const
{
    return {};
}

ComponentInterfaceSymbol AudioUnitEffectsModule::GetSymbol() const
{
    /* i18n-hint: Audio Unit is the name of an Apple audio software protocol */
    return XO("Audio Unit Effects");
}

VendorSymbol AudioUnitEffectsModule::GetVendor() const
{
    return XO("The Audacity Team");
}

wxString AudioUnitEffectsModule::GetVersion() const
{
    // This "may" be different if this were to be maintained as a separate DLL
    return AUDIOUNITEFFECTS_VERSION;
}

TranslatableString AudioUnitEffectsModule::GetDescription() const
{
    return XO("Provides Audio Unit Effects support to Audacity");
}

// ============================================================================
// PluginProvider implementation
// ============================================================================

const FileExtensions& AudioUnitEffectsModule::GetFileExtensions()
{
    static FileExtensions result{ { _T("au") } };
    return result;
}

bool AudioUnitEffectsModule::Initialize()
{
    // Nothing to do here
    return true;
}

void AudioUnitEffectsModule::Terminate()
{
    // Nothing to do here
    return;
}

EffectFamilySymbol AudioUnitEffectsModule::GetOptionalFamilySymbol()
{
#if USE_AUDIO_UNITS
    return AUDIOUNITEFFECTS_FAMILY;
#else
    return {};
#endif
}

void AudioUnitEffectsModule::AutoRegisterPlugins(PluginManagerInterface&)
{
}

PluginPaths AudioUnitEffectsModule::FindModulePaths(PluginManagerInterface&) const
{
    PluginPaths effects;

    LoadAudioUnitsOfType(kAudioUnitType_Effect, effects);
    LoadAudioUnitsOfType(kAudioUnitType_Generator, effects);
    LoadAudioUnitsOfType(kAudioUnitType_Mixer, effects);
    LoadAudioUnitsOfType(kAudioUnitType_MusicEffect, effects);
    LoadAudioUnitsOfType(kAudioUnitType_Panner, effects);

    return effects;
}

unsigned AudioUnitEffectsModule::DiscoverPluginsAtPath(
    const PluginPath& path, TranslatableString& errMsg,
    const RegistrationCallback& callback)
{
    errMsg = {};
    wxString name;
    AudioComponent component = FindAudioUnit(path, name);
    if (component == NULL) {
        errMsg = XO("Could not find component");
        return 0;
    }

    AudioUnitEffectBase effect(path, name, component);
    if (!effect.InitializePlugin()) {
        // TODO:  Is it worth it to discriminate all the ways SetHost might
        // return false?
        errMsg = XO("Could not initialize component");
        return 0;
    }

    if (callback) {
        callback(this, &effect);
    }

    return 1;
}

std::unique_ptr<ComponentInterface>
AudioUnitEffectsModule::LoadPlugin(const PluginPath& path)
{
    // Acquires a resource for the application.
    if (wxString name; auto component = FindAudioUnit(path, name)) {
        auto result = Factory::Call(path, name, component);
        result->InitializePlugin();
        return result;
    }
    return nullptr;
}

bool AudioUnitEffectsModule::CheckPluginExist(const PluginPath& path) const
{
    wxString unused;
    return FindAudioUnit(path, unused) != nullptr;
}

// ============================================================================
// AudioUnitEffectsModule implementation
// ============================================================================

void AudioUnitEffectsModule::LoadAudioUnitsOfType(OSType inAUType,
                                                  PluginPaths& effects) const
{
    AudioComponentDescription desc;
    AudioComponent component;

    desc.componentType = inAUType;
    desc.componentSubType = 0;
    desc.componentManufacturer = 0;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;

    component = AudioComponentFindNext(NULL, &desc);
    while (component != NULL)
    {
        OSStatus result;
        AudioComponentDescription found;

        result = AudioComponentGetDescription(component, &found);
        if (result == noErr) {
            CFStringRef cfName{};
            result = AudioComponentCopyName(component, &cfName);
            CF_ptr<CFStringRef> uName{ cfName };
            if (result == noErr) {
                wxString path;

                path.Printf(wxT("%-4.4s/%-4.4s/%-4.4s/%s"),
                            FromOSType(found.componentManufacturer),
                            FromOSType(found.componentType),
                            FromOSType(found.componentSubType),
                            wxCFStringRef::AsString(cfName));

                for (int i = 0; i < WXSIZEOF(BlackList); ++i) {
                    if (BlackList[i].componentType == found.componentType
                        && BlackList[i].componentSubType == found.componentSubType
                        && BlackList[i].componentManufacturer
                        == found.componentManufacturer) {
                        wxLogDebug(wxT("Blacklisted AU skipped: %s"), path);
                        result = !noErr;
                        break;
                    }
                }
                if (result == noErr) {
                    effects.push_back(path);
                }
            }
        }
        component = AudioComponentFindNext(component, &desc);
    }
}

#endif
